package com.leyou.auth.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.mapper.ApplicationInfoMapper;
import com.leyou.auth.pojo.ApplicationInfo;
import com.leyou.common.auth.pojo.AppInfo;
import com.leyou.common.auth.pojo.Payload;
import com.leyou.common.auth.pojo.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.constants.LyConstants;
import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.pojo.User;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @date: 2021/4/10 0010 0:49
 * @description:
 */
@Service
public class AuthService {
    @Autowired
    private UserClient userClient;
    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ApplicationInfoMapper applicationInfoMapper;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    /**
     * 用户登录
     */
    public void login(String username, String password, HttpServletRequest request, HttpServletResponse response) {
        //校验用户名是否正确
        User loginUser = userClient.query(username, password);
        UserInfo userInfo = new UserInfo(loginUser.getId(),loginUser.getUsername(),"admin");

        //2.生成token，并把token写出到respone中
        this.generateTokenAndWriteCookie(response, userInfo);

    }

    /**
     * 生成token，并把token写出到respone中  ----------------抽取方法---------------
     */
    private void generateTokenAndWriteCookie(HttpServletResponse response, UserInfo userInfo) {
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, jwtProperties.getPrivateKey(), jwtProperties.getCookie().getExpire());

        //把token放入Cookie数据发送给浏览器
        CookieUtils.newCookieBuilder()
                .response(response)
                .name(jwtProperties.getCookie().getCookieName())
                .value(token)
                .domain(jwtProperties.getCookie().getCookieDomain())
                .maxAge(jwtProperties.getCookie().getExpire()*60) //maxAge:单位秒
                .build();
    }


    /**
     * 验证用户是否登录
     */
    public UserInfo verify(HttpServletRequest request, HttpServletResponse response) {
        //从request中取出cookie
        String token = CookieUtils.getCookieValue(request, jwtProperties.getCookie().getCookieName());

        //校验token是否合法
        Payload<UserInfo> payload = null;
        try {
            payload = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey(), UserInfo.class);
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }

        //判断是否为黑名单
        if (redisTemplate.hasKey(LyConstants.BLAK_KEY_PRE + payload.getId())) {
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }

        //取出payload中的userInfo
        UserInfo info = payload.getInfo();

        //刷新过期时间
        //取出当前过期时间  long expireTime = payload.getExpiration().getTime();
        Date expireTime = payload.getExpiration();

        //计算刷新时间  long refreshTime = expireTime - jwtProperties.getCookie().getRefreshTime() * 60 * 1000;
        DateTime refreshTime = new DateTime(expireTime).minusMinutes(jwtProperties.getCookie().getRefreshTime());

        //判断是否超过刷新时间
        if (refreshTime.isBeforeNow()) {
            //重新生成token
            generateTokenAndWriteCookie(response,info);
        }

        //从token中取出登录用户信息
        return info;
    }


    /**
     * 登出用户
     */
    public void logout(HttpServletRequest request, HttpServletResponse response) {
        //1.取出Cookie里面的token
        String token = CookieUtils.getCookieValue(request, jwtProperties.getCookie().getCookieName());

        //2.校验token，取出载荷
        Payload<UserInfo> payload = null;
        payload = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey(), UserInfo.class);


        //3.把token加入黑名单
        addToBackList(payload);

        //4.把浏览器token删除
        CookieUtils.deleteCookie(jwtProperties.getCookie().getCookieName(),jwtProperties.getCookie().getCookieDomain(),response);

    }

    //3.把token加入黑名单 ---------------抽取方法------------------
    private void addToBackList(Payload<UserInfo> payload) {
        //3.1把token的id加入redis作为黑名单key
        String tokenId = payload.getId();
        //3.2获取token过期时间
        Date expiration = payload.getExpiration();
        //3.3计算剩余有效时间
        long remainTime = expiration.getTime() - System.currentTimeMillis();
        //3.4把剩余时间当成过期时间存入redis
        redisTemplate.opsForValue().set(LyConstants.BLAK_KEY_PRE + tokenId,"1",remainTime, TimeUnit.MILLISECONDS);
    }

    /**
     * 校验服务名称和服务密码是否正确
     */
    public ApplicationInfo checkServiceNameAndSecret(String serviceName, String secret){
        //校验服务名是否存在
        ApplicationInfo applicationInfo = new ApplicationInfo();
        applicationInfo.setServiceName(serviceName);
        QueryWrapper<ApplicationInfo> queryWrapper = Wrappers.query(applicationInfo);
        ApplicationInfo loginAppInfo = applicationInfoMapper.selectOne(queryWrapper);
        if (loginAppInfo==null) {
            throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
        }
        //判断密码是否一致
        if (!passwordEncoder.matches(secret,loginAppInfo.getSecret())) {
            throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
        }
        return loginAppInfo;
    }


    /**
     * 给其它微服务签发token
     */
    public String authorization(String serviceName, String secret) {
        //校验服务是否存在
        ApplicationInfo loginAppInfo = checkServiceNameAndSecret(serviceName,secret);

        //查询当前服务的目标服务列表
        List<String> targetList = applicationInfoMapper.findTargetListByServiceId(loginAppInfo.getId());

        //生成并返回token
        AppInfo appInfo = new AppInfo(loginAppInfo.getId(), loginAppInfo.getServiceName(), targetList);
        String token = JwtUtils.generateTokenExpireInMinutes(appInfo,jwtProperties.getPrivateKey(),jwtProperties.getApp().getExpire());

        return token;
    }
}
